package cn.sciento.boot.tenant.service.impl;

import cn.sciento.core.convertor.ApplicationContextHelper;
import cn.sciento.core.exception.CommonException;
import com.google.common.base.Throwables;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import cn.sciento.boot.tenant.autoconfigure.TenantInitProperties;
import cn.sciento.boot.tenant.constant.TenantInitProcessorType;
import cn.sciento.boot.tenant.constant.TenantInitStatus;
import cn.sciento.boot.tenant.constant.TenantInitType;
import cn.sciento.boot.tenant.dto.TenantInitConfigDTO;
import cn.sciento.boot.tenant.dto.TenantInitLogDTO;
import cn.sciento.boot.tenant.dto.TenantInitProcessContext;
import cn.sciento.boot.tenant.processor.TenantInitGroupExecutor;
import cn.sciento.boot.tenant.processor.TenantInitProcessor;
import cn.sciento.boot.tenant.service.TenantInitLogSendService;
import cn.sciento.boot.tenant.service.TenantInitService;
import cn.sciento.core.redis.RedisHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

@Service
public class TenantInitServiceImpl implements TenantInitService {
  private static final Logger LOGGER = LoggerFactory.getLogger(TenantInitServiceImpl.class);
  
  @Autowired
  private RedisHelper redisHelper;
  
  @Autowired
  private Environment environment;
  
  @Autowired
  @Qualifier("tenantInitAsyncTaskExecutor")
  private AsyncTaskExecutor tenantInitAsyncTaskExecutor;
  
  @Autowired
  private TenantInitLogSendService tenantInitLogService;
  
  @Autowired
  private TenantInitProperties tenantInitProperties;
  
  @Override
  public void process(TenantInitProcessContext tenantInitProcessContext) {
    if (this.tenantInitProperties == null || !this.tenantInitProperties.getProcess().isEnabled()) {
      return;
    }
    Assert.notNull(tenantInitProcessContext, "tenant init process context cannot be null.");
    Assert.notNull(tenantInitProcessContext.getTenantInitContext(), "tenant init context cannot be null.");
    List<TenantInitConfigDTO> tenantInitConfigDTOs = tenantInitProcessContext.getTenantInitConfigDTOs();
    if (CollectionUtils.isEmpty(tenantInitConfigDTOs)) {
      LOGGER.info("tenant {} init current process doesn't have any processors, exit.", tenantInitProcessContext.getTenantInitContext());
      return;
    } 
    List<TenantInitConfigDTO> effectiveTenantInitConfigDTOs = validate(tenantInitConfigDTOs);
    if (CollectionUtils.isEmpty(effectiveTenantInitConfigDTOs)) {
      LOGGER.info("after validate, tenant {} init current process doesn't have any effective processors, exit.", tenantInitProcessContext.getTenantInitContext());
      return;
    } 
    TenantInitGroupExecutor.doGroupThenExecuteOrderly(this.tenantInitAsyncTaskExecutor, effectiveTenantInitConfigDTOs, TenantInitConfigDTO::getOrderSeq, (orderSeq, item) -> {
          TenantInitLogDTO tenantInitLogDTO = TenantInitLogDTO.buildInstance(item, tenantInitProcessContext.getTenantInitContext());
          try {
            TenantInitProcessor tenantInitProcessor = ApplicationContextHelper.getContext().getBean(item.getProcessorCode(), TenantInitProcessor.class);
            tenantInitProcessor.process(tenantInitProcessContext.getTenantInitContext());
            tenantInitLogDTO.setProcessStatus(TenantInitStatus.S);
          } catch (Exception e) {
            tenantInitLogDTO.setProcessMessage(Throwables.getStackTraceAsString(Throwables.getRootCause(e)));
            tenantInitLogDTO.setProcessStatus(TenantInitStatus.E);
            throw e;
          } finally {
            this.tenantInitLogService.asyncInsertTenantInitLog(tenantInitLogDTO);
          } 
          return item;
        });
  }

  /**
   * 对相关的配置进行过滤校验
   * @param tenantInitConfigDTOs
   * @return
   */
  private List<TenantInitConfigDTO> validate(List<TenantInitConfigDTO> tenantInitConfigDTOs) {
    Assert.notEmpty(tenantInitConfigDTOs, "tenant init processor cannot be empty.");
    String serviceName = this.environment.getProperty("spring.application.name");
    // 过滤应用
    List<TenantInitConfigDTO> effectiveTenantInitConfigDTOs = tenantInitConfigDTOs.parallelStream().filter(item -> item.getServiceName().equals(serviceName)).collect(Collectors.toList());
    Set<TenantInitType> tenantInitTypes = effectiveTenantInitConfigDTOs.parallelStream().map(TenantInitConfigDTO::getInitType).collect(Collectors.toSet());
    Set<TenantInitProcessorType> tenantInitProcessorTypes = effectiveTenantInitConfigDTOs.parallelStream().map(TenantInitConfigDTO::getProcessorType).collect(Collectors.toSet());
    if (tenantInitTypes.size() > 1 || tenantInitProcessorTypes.size() > 1) {
      LOGGER.error("current service {} processor execution roundtrip must be unique init type and init processor type(initTypes={}, initProcessorTypes={}).", serviceName, tenantInitTypes, tenantInitProcessorTypes);
      throw new CommonException("current service processor execution roundtrip must be unique init type and init processor type.");
    } 
    return effectiveTenantInitConfigDTOs;
  }
}
